﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using JetBrains.Annotations;
using Kaleida.ServiceMonitor.Framework;
using Kaleida.ServiceMonitor.Model;
using Kaleida.ServiceMonitor.Model.Parsing;
using Kaleida.ServiceMonitor.Model.Runtime;
using log4net;
using System.Linq;
using Monitor = Kaleida.ServiceMonitor.Model.Monitor;
using Timer = System.Timers.Timer;

namespace Kaleida.ServiceMonitor.UI
{
    public partial class MainForm : Form
    {
        private readonly Monitor monitor;

        private readonly string initialScriptName;
        
        private bool autoStart;
        
        private bool windowIsActive = true;

        private static readonly ILog log = LogManager.GetLogger(typeof (MainForm));
        private readonly NotifyIcon trayIcon;
        private readonly ContextMenu trayMenu;
        private readonly Timer pollTimer = new Timer();
        private bool updatingUI;
        readonly IList<INotificationPanel> notificationPanels;
        readonly ISet<object> naughtyList = new HashSet<object>();
        private AboutControl aboutControl;
        private readonly IList<IServiceMonExtension> operationWatchers;

        public MainForm([CanBeNull]string scriptName = "")
        {
            var configuration = new FileBasedConfiguration();
            monitor = new Monitor(configuration);
            initialScriptName = scriptName ?? configuration.InitialScriptName ?? "";

            Application.ThreadException += UnhandledExceptionHandler;

            TryInitialiseFromConfig();

            trayMenu = new ContextMenu();
            trayMenu.MenuItems.Add("Exit", TrayIconMenuExitClicked);

            trayIcon = new NotifyIcon {ContextMenu = trayMenu, Visible = true};
            trayIcon.MouseUp += OnTrayIconClick;

            InitializeComponent();

            lblScriptSummary.Text = "";
            
            workspaceControl.Workspace = monitor.Workspace;
            workspaceControl.ScriptDirectoryPath = ServiceMon.ScriptPath;

            monitor.ScriptCompilationComplete += ScriptCompilationComplete;
            monitor.Workspace.CurrentScriptChanged += SelectedScriptChanged;

            monitor.State.MonitorStarted += OnStartedMonitoring;
            monitor.State.MonitorStopped += OnStoppedMonitoring;
            
            monitor.OperationSuccessful += OperationSuccessful;
            monitor.OperationFailed += OperationFailed;
            monitor.ErrorsAcknowledged += ErrorsAcknowledged;
            
            operationWatchers = BuildOperationWatchers();


            notificationPanels = NotificationPanelFactory.BuildPanels();
            AddNotificationTabPanels();
            AddHelpTabPage();
        }

        private IList<IServiceMonExtension> BuildOperationWatchers()
        {
            var watchers = new List<IServiceMonExtension>
                               {
                                   new ThresholdChangeEmailer(monitor),
                                   new ScheduledSummaryEmailer(monitor)
                               };

            return watchers;
        }

        private void AddNotificationTabPanels()
        {
            var notificationTabPages = notificationPanels.Select(BuildAndInitialiseNotificationPanel);
            tabControl.TabPages.AddRange(notificationTabPages.Cast<TabPage>().ToArray());
        }

        private NotificationPanelTabPage BuildAndInitialiseNotificationPanel(INotificationPanel panel)
        {
            var page = new NotificationPanelTabPage(panel);
            panel.Initialise(monitor);
            return page;
        }

        private void AddHelpTabPage()
        {
            aboutControl = new AboutControl {Dock = DockStyle.Fill};
            var tabPage = new TabPage("Help");
            tabPage.Controls.Add(aboutControl);
            tabControl.TabPages.Add(tabPage);
        }

        public void TryInitialiseFromConfig()
        {
            try
            {
                autoStart = monitor.Configuration.AutoStart;
            }
            catch (Exception exception)
            {
                MessageBox.Show(string.Format("Error Reading config: {0}", exception));
            }
        }

        private void UnhandledExceptionHandler(object sender, ThreadExceptionEventArgs e)
        {
            log.Fatal(e.Exception);
            MessageBox.Show(string.Format("ERROR: {0}\r\nSee log files for more information", e.Exception.Message), "An unexpected error occured", MessageBoxButtons.OK, MessageBoxIcon.Error);
            Application.Exit();
        }

        protected override void OnLoad(EventArgs e)
        {
            ScriptParser.Initialise(ServiceMon.OperationsPath);
            
            aboutControl.PopulateOperationsHelp(ScriptParser.GetRuntimeEnvironment());
            
            UpdateWindowTitle();

            Icon = ApplicationIcon.Application;

            components = new Container();
            components.Add(trayIcon);

            pollTimer.Elapsed += PollTimerTick;

            workspaceControl.LoadScriptsFromDisk();
            workspaceControl.TrySelectScript(initialScriptName);

            monitor.AcknowledgeErrors();

            if (autoStart && monitor.State.HasSuccessfullyCompiledScript)
            {
                StartMonitoring();
            }
            else
            {
                StopMonitoring();
            }

            Visible = false;

            ThreadPool.QueueUserWorkItem(i => CheckForNewVersion());

            base.OnLoad(e);
        }

        private void CheckForNewVersion()
        {
            var versionCheck = new CodePlexWebVersionCheck();
            var result = versionCheck.CheckLatestVersion();

            BeginInvoke(new EventHandler(delegate
                                             {
                                                 newVersionLink.Text = "";
                                                 if (!result.Success)
                                                 {
                                                     aboutControl.SetVersionCheckStatus("Could not check for new version", result.Exception.Message);
                                                 }
                                                 else
                                                 {
                                                     if (ServiceMon.VersionNumber == result.LatestVersionNumber)
                                                     {
                                                         aboutControl.SetVersionCheckStatus("This is the latest recommended version");
                                                     }
                                                     else if (ServiceMon.VersionNumber > result.LatestVersionNumber)
                                                     {
                                                         aboutControl.SetVersionCheckStatus("This is a pre-release version. The latest recommended version is v" + result.LatestVersionNumber);
                                                     }
                                                     else
                                                     {
                                                         var linkText = string.Format("Version '{0}' is available. Click to download", result.LatestVersionNumber);
                                                         
                                                         newVersionLink.Text = linkText;
                                                         newVersionLink.Tag = result.LatestVersionUrl;
                                                         newVersionLink.Visible = true;
                                                     }
                                                 }
                                             }));
        }

        private void UpdateWindowTitle()
        {
            var title = new StringBuilder();

            title.AppendFormat("{0} - ", monitor.Workspace.CurrentScript.Name);

            title.AppendFormat("ServiceMon v{0}", ServiceMon.VersionNumber);
            Text = title.ToString();
        }

        private void OperationSuccessful(object sender, OperationSuccessfulEventArgs e)
        {
            SendMessageToExtensions(i => i.OnOperationSuccessful(e));
        }

        private void OperationFailed(object sender, OperationFailedEventArgs e)
        {
            SendMessageToExtensions(i => i.OnOperationFailed(e));
        }

        private void ErrorsAcknowledged(object sender, EventArgs e)
        {
            SendMessageToExtensions(i => i.OnErrorsAcknowledged());
        }

        private void SelectedScriptChanged(object sender, EventArgs e)
        {
            SendMessageToNotificationPanels(i => i.RefreshContent());

            UpdateWindowTitle();

            tabControl.SelectTab(tabPageScript);
        }

        private void SendMessageToExtensions(Action<IServiceMonExtension> message)
        {
            foreach (var watcher in operationWatchers)
            {
                if (naughtyList.Contains(watcher)) continue;

                try
                {
                    message(watcher);
                }
                catch (Exception exception)
                {
                    naughtyList.Add(watcher);
                    log.Error(string.Format("OperationWatcher '{0}' threw an unhandled exception: {1}", watcher.GetType().Name, exception));
                }
            }
        }

        private void SendMessageToNotificationPanels(Action<INotificationPanel> message)
        {
            foreach (var notificationPanel in notificationPanels)
            {
                if (naughtyList.Contains(notificationPanel)) continue;

                try
                {
                    message(notificationPanel);
                }
                catch (Exception exception)
                {
                    naughtyList.Add(notificationPanel);

                    log.Error(string.Format("Panel '{0}' threw an unhandled exception: {1}", notificationPanel.PanelName, exception));
                    notificationPanel.GetPanelControl().Controls.Clear();

                    var errorMessage = new StringBuilder();
                    errorMessage.AppendFormat("An unhandled Exception occured: {0}\r\n\r\n", exception.Message);
                    errorMessage.AppendFormat("This panel will not be sent new messages until ServiceMon is restarted\r\n");
                    errorMessage.AppendFormat("Please see logs for more information");

                    var errorLabel = new Label
                                         {
                                             AutoSize = true,
                                             ForeColor = Color.Red,
                                             Text = errorMessage.ToString()
                                         };

                    notificationPanel.GetPanelControl().Controls.Add(errorLabel);
                }
            }
        }

        private void ScriptCompilationComplete(object sender, ScriptCompilationCompleteEventArgs args)
        {
            var compilation = args.ScriptCompilation;

            lock (this)
            {
                if (compilation.Succeeded)
                {

                    monitor.Statistics.Initialise(compilation.CompiledScript.Operations);
                    pollFrequencyControl.Value = compilation.CompiledScript.PollFrequency;

                    SendMessageToExtensions(i => i.ScriptCompilationComplete(args));
                }
            }
        }


        private void OnTrayIconClick(object o, EventArgs e)
        {
            ShowInTaskbar = true;
            Visible = true;
            WindowState = FormWindowState.Normal;
        }

        protected override void OnResize(EventArgs e)
        {
            if (WindowState == FormWindowState.Minimized)
            {
                ShowInTaskbar = false;
                Visible = false;
            }
            else
            {
                base.OnResize(e);
            }
        }

        private void TrayIconMenuExitClicked(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void StartMonitoring()
        {
            if (!monitor.State.CanBeginMonitoring)
                return;

            monitor.State.StartMonitoring();

            if (tabControl.SelectedTab == tabPageScript)
            {
                TrySelectFirstNotificationPanel();
            }

            ThreadPool.QueueUserWorkItem(i => monitor.InvokeNextOperation());
        }

        private void TrySelectFirstNotificationPanel()
        {
            var firstPanel = tabControl.TabPages.Cast<TabPage>().FirstOrDefault(i => i is NotificationPanelTabPage);
            if (firstPanel != null)
            {
                tabControl.SelectTab(firstPanel);
            }
        }

        private void StopMonitoring()
        {
            if (!monitor.State.IsMonitoring)
                return;

            monitor.State.StopMonitoring();
            
            if (monitor.State.ScriptCompilation != null && monitor.State.ScriptCompilation.Succeeded)
                pollFrequencyControl.Value = monitor.State.ScriptCompilation.CompiledScript.PollFrequency;
        }

        private void OnStartedMonitoring(object sender, EventArgs args)
        {
            lock (this)
            {
                SendMessageToExtensions(i => i.OnStartedMonitoring());
                UpdateTimerFromState();
            }
        }

        private void OnStoppedMonitoring(object sender, EventArgs args)
        {
            lock (this)
            {
                UpdateTimerFromState();
            }
        }

        private void UpdateTimerFromState()
        {
            pollTimer.Interval = PollIntervalMilliseconds;
            pollTimer.Enabled = monitor.State.IsMonitoring;
        }

        private string GetSystemTrayTitle(string message)
        {
            var title = new StringBuilder();
            title.AppendFormat("{0}: ", monitor.Workspace.CurrentScript.Name);
            title.Append(message);

            return title.ToString();
        }

        private void SetSystemTray(string message, Icon icon)
        {
            var text = GetSystemTrayTitle(message);

            var shortText = text.Abbreviate(60);

            text = shortText;

            trayIcon.Icon = new Icon(icon, 16, 16);
            trayIcon.Text = text;
        }

        private void UpdateCurrentScriptSummary()
        {
            var compilation = monitor.State.ScriptCompilation;
            if (compilation == null)
            {
                lblScriptSummary.Text = "";
                lblScriptSummary.ForeColor = Color.Black;
            }
            else if (compilation.Succeeded)
            {
                lblScriptSummary.Text = compilation.CompiledScript.BuildSummary();
                lblScriptSummary.ForeColor = Color.Black;
            }
            else
            {
                var message = "Cannot Parse Script: " + compilation.Exception.Message;
                SetSystemTray(message, ApplicationIcon.Error);

                lblScriptSummary.Text = message;
                lblScriptSummary.ForeColor = Color.Red;
            }
        }

        private void UpdateUIFromModel()
        {
            lblPluginErrorSummary.Text = naughtyList.Any() ? "Error with plugin. Please see log." : "";
            lblPluginErrorSummary.Visible = naughtyList.Any();

            btnStart.Enabled = monitor.State.CanBeginMonitoring;
            btnStop.Enabled = monitor.State.IsMonitoring;
            
            pollFrequencyControl.Enabled = monitor.State.IsMonitoring;

            btnAckError.Enabled = monitor.State.HasOperationFailures;

            var focusedControl = FindFocusedControl(this);
            SendMessageToNotificationPanels(i=>i.RefreshContent());

            if (windowIsActive &&  focusedControl!= null)
            {
                focusedControl.Focus();
            }
            
            Icon systemTrayIcon = GetSystemTrayIcon();

            string statusSummary = monitor.State.DefaultPresentation.GetStateSummary();
            SetSystemTray(statusSummary, systemTrayIcon);

            string statusBarMessage = statusSummary + (monitor.State.IsMonitoring ? " " + GetSpinner() : "");
            tsStatusLabel.SetMessage(statusBarMessage, monitor.State.HasScriptError || monitor.State.HasOperationFailures ? Color.Red : monitor.State.IsMonitoring ? Color.Green : Color.Black);

            UpdateCurrentScriptSummary();
        }

        [CanBeNull]
        public static Control FindFocusedControl(Control control)
        {
            var container = control as ContainerControl;
            while (container != null)
            {
                control = container.ActiveControl;
                container = control as ContainerControl;
            }
            return control;
        }

        private Icon GetSystemTrayIcon()
        {
            return monitor.State.HasOperationFailures || monitor.State.HasScriptError
                       ? ApplicationIcon.Error
                       : monitor.State.IsMonitoring ? ApplicationIcon.Running : ApplicationIcon.Stopped;
        }

        private string GetSpinner()
        {
            const int width = 100;
            var position = monitor.Statistics.Combined.ProcessedCount % width;

            return new string('.', position) + new string(' ', width - position);
        }

        private int PollIntervalMilliseconds
        {
            get
            {
                var duration = pollFrequencyControl.Value;
                return (int) (duration.ToTimeSpan().TotalMilliseconds);
            }
        }

        private void StartButtonClick(object sender, EventArgs e)
        {
            log.DebugFormat("Starting to poll every {0}ms", PollIntervalMilliseconds);
            StartMonitoring();
        }

        private void StopButtonClick(object sender, EventArgs e)
        {
            log.Debug("Stopping poll");
            StopMonitoring();
        }

        private void UIUpdateTimerTick(object sender, EventArgs e)
        {
            if (updatingUI) return;

            updatingUI = true;
            UpdateUIFromModel();
            updatingUI = false;
        }

        private void PollTimerTick(object sender, EventArgs e)
        {
            monitor.InvokeNextOperation();
        }

        private void pollFrequencyControl_ValueChanging(object sender, PollFrequencyValueChangingEventArgs e)
        {
            e.NewValue = PollFrequencyLimit.EnsureMet(e.NewValue);
        }

        private void pollFrequencyControl_ValueChanged(object sender, EventArgs e)
        {
            pollTimer.Interval = PollIntervalMilliseconds;
        }

        private void AcknowledgeErrorClick(object sender, EventArgs e)
        {
            monitor.AcknowledgeErrors();
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            e.Cancel = workspaceControl.AllowUserToSaveDirty();
        }
        
        private static void BrowseTo(string url)
        {
            var sInfo = new ProcessStartInfo(url);
            Process.Start(sInfo);
        }

        private void newVersionLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            var url = (string) newVersionLink.Tag;

            if (!string.IsNullOrEmpty(url))
            {
                BrowseTo(url);
            }
        }

        private void MainForm_Enter(object sender, EventArgs e)
        {
            windowIsActive = true;
        }

        private void MainForm_Leave(object sender, EventArgs e)
        {
            windowIsActive = false;
        }

        private void MainForm_Resize(object sender, EventArgs e)
        {
            var isMini = Height < 300 || Width < 580;

            statusStrip.Visible = !isMini;
            pnlTopBar.Visible = !isMini;
            newVersionLink.Visible = newVersionLink.Text != "" && !isMini;

            if (isMini)
            {
                tabControl.Dock = DockStyle.Fill;
            }
            else
            {
                tabControl.Dock = DockStyle.None;
                tabControl.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
                tabControl.Location = new Point(0, 36);
                tabControl.Size = new Size(Width - 16, Height - 100);
            }

            lblScriptSummary.Visible = !(Height < 450);
        }
    }
}
